import hashlib, hmac, time, os, json
from http import encode, loadjson, readurl, openurl, randstr, uploadfile
from errors import check_status, KuaiPanError

def _sign(consumer_secret, http_method='GET', base_uri='', parameters={}, oauth_token_secret=''):
    param = encode(parameters)
    param.sort()
    base_string = "%s&%s&%s" % (http_method, encode(base_uri), encode("&".join(param)))
    key = str(consumer_secret + '&' + oauth_token_secret)
    return hmac.new(key, base_string, hashlib.sha1).digest().encode('base64').rstrip()

def _readjson(url, timeout=30):
    data, status = readurl(url, timeout=timeout)
    check_status(status, data)
    return loadjson(data)

class KuaiPanAPI:
    def __init__(self, consumer_key='', consumer_secret='', root='kuaipan',  oauth_token='',  oauth_token_secret='', difftime=0, timeout=30):
        '''
        Create KuaiPanAPI instance.
        You should at least provide <consumer_key> and <consumer_secret>.
        
        consumer_key:       the developer id from http://www.kuaipan.cn/developers/.
        consumer_secret:    the private key of <consumer_key>, got from http://www.kuaipan.cn/developers/.
        root:               ['kuaipan' | 'app_folder'], the applicaiton's root folder, default is 'kuaipan'.
        oauth_token:        the access token authorized by kuaipan's user.
        oauth_token_secret: the private key of <oauth_token>.
        difftime:           the time difference between local time and server time.
                            Set this to adjust the value of <oauth_timestamp>.
                            You can call KuaiPanAPI.open_time() to get server time.
                            difftime = server time - local time.
                            For example:
                                server_time = KuaiPanAPI.open_time()
                                difftime = int(server_time.get('Timestamp')) - int(time())
        timeout:            the timeout of http connect.
        '''
        self.difftime = difftime
        self.root = root
        self.consumer_secret = consumer_secret
        self.consumer_key = consumer_key
        self.oauth_token = oauth_token
        self.oauth_token_secret = oauth_token_secret
        self.charged_dir = ''
        self.user_id = -1
        self.expires_in = 0
        self.oauth_verifier = ''
        self.temp_oauth_token = ''
        self.temp_oauth_token_secret = ''
        self.timeout = timeout

    def _make_url(self, base_uri='', parameters={}, oauth_token_secret=None, http_method='GET'):
        param = parameters.copy()
        if oauth_token_secret is None:
            oauth_token_secret = self.oauth_token_secret
        param['oauth_signature'] = _sign(consumer_secret=self.consumer_secret, http_method=http_method, base_uri=base_uri, parameters=parameters, oauth_token_secret=oauth_token_secret)
        return "%s?%s" % (base_uri, "&".join(encode(param, '/%.-_~')))

    def _oauth_param(self):
        return {'oauth_nonce': randstr(31),
                 'oauth_timestamp': str(int(time.time()) + self.difftime),
                 'oauth_consumer_key': self.consumer_key,
                 'oauth_signature_method': 'HMAC-SHA1',
                 'oauth_version': '1.0',
                 'oauth_token': self.oauth_token}
         
    @classmethod    
    def open_time(cls):
        '''
        Get kuipan server's timestamp, to adjust <oauth_timestamp>.
        
        Return a dict like
            {
                'Timestamp': _, 
            }
        '''
        url = 'http://openapi.kuaipan.cn/open/time'
        data, status = readurl(url)
        check_status(status, data)
        return loadjson(data)
    
    def open_request_token(self, oauth_callback='http://127.0.0.1'):
        '''
        Request temporary token.
        
        oauth_callback: the call back URI used after user authorized. 
                        Set None, if you don't need to call back.
                        But if you want to use auto authorization (KuaiPanAPI.authorize), you should set this to any URI.
       
        Return a dict like
            {
                'oauth_token_secret': _,
                'oauth_token': _, 
                'oauth_callback_confirmed': _
            }
                        
        '''
        base_uri = "https://openapi.kuaipan.cn/open/requestToken"
        param = self._oauth_param()
        self.oauth_callback = oauth_callback
        if oauth_callback is not None:
            param['oauth_callback'] = self.oauth_callback
        param.pop('oauth_token')
        url = self._make_url(base_uri, param, '')
        resp = _readjson(url, timeout=self.timeout)
        if type(resp) is dict:
            self.temp_oauth_token = resp.get('oauth_token')
            self.temp_oauth_token_secret = resp.get('oauth_token_secret')
        return resp

    def authorize(self, username="", userpwd=""):
        '''
        Auto Authorize based on web page analyzing and auto post.
        
        username: kuaipan's username.
        userpwd:  kuaipan user's password.
        
        Return user's authorization verifier.
        '''
        import urllib2, urlparse, re, cookielib
        class RedirectHandler(urllib2.HTTPRedirectHandler):
            def __init__(self, kuaipan):
                self.kuaipan = kuaipan
            def _get_verifier(self, headers):
                location = headers.get('location', '')
                redirect_url = urlparse.urlparse(location)
                param = urlparse.parse_qs(redirect_url.query)
                if self.kuaipan.temp_oauth_token == param.get('oauth_token', [''])[0]:
                    self.kuaipan.oauth_verifier = param.get('oauth_verifier', [''])[0]
            def http_error_301(self, req, fp, code, msg, headers):
                self._get_verifier(headers)
                return urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers)
            def http_error_302(self, req, fp, code, msg, headers):
                self._get_verifier(headers)
                return urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
                 
        cj = cookielib.LWPCookieJar()
        cookie_support = urllib2.HTTPCookieProcessor(cj)
        opener = urllib2.build_opener(cookie_support, urllib2.HTTPHandler, RedirectHandler(self))
        #request login page
        url = 'https://www.kuaipan.cn/api.php?ac=open&op=authorise&oauth_token=' + self.temp_oauth_token
        try:
            login_page, _ = readurl(url, opener=opener, timeout=self.timeout)
        except Exception, e:
            raise KuaiPanError(str(e))
            
        #auto login
        hiddens = re.findall(r'<input\s+type="hidden"\s+name="(?P<name>\w*)"\s+value="(?P<value>\w*)"\s*/?>', login_page)
        data = {n: v for n, v in hiddens}
        data['username'] = username
        data['userpwd'] = userpwd
        data = '&'.join(encode(data))
        referer = url
        url = 'https://www.kuaipan.cn/api.php?ac=open&op=authorisecheck'
        try:
            data, _ = readurl(url, data, referer, opener, timeout=self.timeout)
        except Exception, e:
            pass
        return self.oauth_verifier
    
    def open_access_token(self, oauth_verifier):
        '''
        Get access token
        
        oauth_verifier: kuaipan user's authorization veifier, can get from KuaiPanAPI.authorize().
        
        Return a dict like
            {
                'oauth_token_secret': _,
                'oauth_token': _, 
                'user_id': _
            }
        '''
        base_uri = "https://openapi.kuaipan.cn/open/accessToken"
        param = self._oauth_param()
        param['oauth_token'] = self.temp_oauth_token
        param['oauth_verifier'] = oauth_verifier
        url = self._make_url(base_uri, param, self.temp_oauth_token_secret)
        resp = _readjson(url, timeout=self.timeout)

        if type(resp) is dict:
            self.oauth_token = resp.get('oauth_token')
            self.charged_dir = resp.get('charged_dir')
            self.user_id = resp.get('user_id')
            self.expires_in = resp.get('expires_in')
            self.oauth_token_secret = resp.get('oauth_token_secret')
        return resp
    
    def account_info(self):
        '''
        Get account info.
        
        Return a dict like
            {
                'max_file_size': _, 
                'user_name': _, 
                'quota_used': _, 
                'quota_total': _, 
                'user_id': _
            }
        '''
        base_uri = "http://openapi.kuaipan.cn/1/account_info"
        param = self._oauth_param()
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def metadata(self, path=u'/', listfiles=True, file_limit=10000, page=0, page_size=20, filter_ext=None, sort_by=None):
        '''
        Get the metadata of file or folder
        
        path:       file or folder path from <root>.
        listfiles:  [True | False] if list folder's files, default is True.
        file_limit: the max number of folder's files
        page:       0 means don't page files, > 0 means files will be paged, and <page> is the page number.
        page_size:  how many items in each page, work when paging (<page> > 0), default is 20
        filter_ext: only return the files with <filter_ext> extend file name.
                    <filter_ext> only accept ASCII cheracters and length <= 5.
                    such as: 'jpg', 'jpeg', 'png', 'bmp' and etc.
        sort_by:    [None | 'date' | 'name' | 'size'], work when paging (<page> > 0).
                     default is None, means do not sorting.
        
        Return a dict like
            {
                'path': _, 
                'root': _, 
                'hash': _, 
                'file_id': _, 
                'type': _,
                'size': _,
                'create_time': _,
                'modify_time': _,
                'name': _,
                'rev': _,
                'is_deleted': _,
                'files': [
                    {
                        'file_id': _, 
                        'type'
                        'size': _,
                        'create_time': _,
                        'modify_time': _,
                        'name': _,
                        'rev': _,
                        'is_deleted': _,
                    },
                ]
            }
        '''
        base_uri = "http://openapi.kuaipan.cn/1/metadata/" + self.root + encode(path)
        param = self._oauth_param()
        param['list'] = 'true' if listfiles else 'false' 
        param['file_limit'] = str(file_limit)
        param['page'] = str(page)
        param['page_size'] = str(page_size)
        if filter_ext is not None:
            param['filter_ext'] = filter_ext
        if sort_by is not None:
            param['sort_by'] = sort_by
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def shares(self, path, name=None, access_code=None):
        '''
        Share file
        
        path:        file path.
        name:        the name showed on downloading page.
        access_code: the password for downloading this file, None means no password.
        
        Return a dict like
            {
                'url': _,
            } 
        '''
        base_uri = "http://openapi.kuaipan.cn/1/shares/" + self.root + encode(path)
        param = self._oauth_param()
        if name is not None:
            param['name'] = name
        if access_code is not None:
            param['access_code'] = access_code
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def history(self, path):
        '''
        Get the version history of file
        
        path: file path
        
        
        Return a dict like
            {
                'files': [
                    {
                        'file_id': _,
                        'rev': _,
                        'create_time': _,
                    },
                ],
            } 
        '''
        base_uri = "http://openapi.kuaipan.cn/1/history/" + self.root + encode(path)
        param = self._oauth_param()
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def fileops_create_folder(self, path):
        '''
        Create folder.
        
        path: file path.
        
        Return a dict like
            {
                'path': _,
                'file_id': _,
                'root': _,
            } 
        '''
        base_uri = "http://openapi.kuaipan.cn/1/fileops/create_folder"
        param = self._oauth_param()
        param['root'] = self.root
        param['path'] = path.encode('utf-8')
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def fileops_delete(self, path, to_recycle=True):
        '''
        Delete file or folder.
        
        path: file or folder path.
        to_recycle: [True | False] if put the deleted file (or folder) to recycle, default is True.
        '''
        base_uri = "http://openapi.kuaipan.cn/1/fileops/delete"
        param = self._oauth_param()
        param['root'] = self.root
        param['path'] = path.encode('utf-8')
        param['to_recycle'] = 'true' if to_recycle else 'false'
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def fileops_move(self, from_path, to_path):
        '''
        Move file or folder. Cannot move shared files or folders, and cannot move into loop path. 
        
        from_path: the source path of file or folder.
        to_path:   the destination path of file or folder.
                   rename the target if from_path and to_path in the same folder.
        '''
        base_uri = "http://openapi.kuaipan.cn/1/fileops/move"
        param = self._oauth_param()
        param['root'] = self.root
        param['from_path'] = from_path.encode('utf-8')
        param['to_path'] = to_path.encode('utf-8')
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def fileops_copy(self, from_path, to_path, from_copy_ref=None):
        '''
        Copy file or folder.
        
        from_path:     the source path of file or folder. if None, use <from_copy_ref>.
        to_path:       the destination path of file or folder.
        from_copy_ref: copy ref, used when <from_path> is None, got from KuaiPanAPI.copy_ref().
        
        Return a dict like
            {
                'file_id': _,
            } 
        '''
        base_uri = "http://openapi.kuaipan.cn/1/fileops/copy"
        param = self._oauth_param()
        param['root'] = self.root
        if from_path is not None:
            param['from_path'] = from_path.encode('utf-8')
        else:
            param['from_copy_ref'] = from_copy_ref
        param['to_path'] = to_path.encode('utf-8')
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)
    
    def copy_ref(self, path):
        '''
        Get copy ref, which used in file (folder) coping.
        
        path: the file or folder path.
        
        Return a dict like
            {
                'copy_ref': _,
                'expires': _,
            } 
        '''
        base_uri = "http://openapi.kuaipan.cn/1/copy_ref/" + self.root + encode(path)
        param = self._oauth_param()
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def fileops_upload_locate(self, source_ip=None):
        '''
        Get the base URL for uploading files.
        
        source_ip: client IP, used by server to dispatch uploading, can by None.
        
        Return a dict like
            {
                'url': _,
            } 
        '''
        base_uri = "http://api-content.dfs.kuaipan.cn/1/fileops/upload_locate"
        param = self._oauth_param()
        if source_ip is not None:
            param['source_ip'] = str(source_ip)
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)

    def fileops_upload_file(self, url, overwrite, path, f, size, callback=None):
        '''
        Upload file
        
        url:       upload URL, got from KuaiPanAPI.fileops_upload_locate()
        overwrite: [True | False], if overwrite the exist file.
        path:      file path.
        f:         a file-like object.
        size:      file size.
        callback:  a callback function to get the uploading progress, can be None.
                   callback function should like this:
                   def uploadcb(total, sent):
                        print ("File Uploading : %s / %s" % (sent, total))
        
         Return a dict like
            {
                'file_id': _, 
                'type': _,
                'size': _,
                'create_time': _,
                'modify_time': _,
                'name': _,
                'rev': _,
                'is_deleted': _,
            }
        '''
        base_uri = url + "1/fileops/upload_file"
        param = self._oauth_param()
        param['root'] = self.root
        param['path'] = path.encode('utf-8')
        param['overwrite'] = 'true' if overwrite else 'false'
        url = self._make_url(base_uri, param, http_method='POST')
        filename = os.path.basename(path)
        filename = filename.encode('utf-8')
        data, status = uploadfile(url, f, size, 'file', filename, None, self.timeout, callback=callback)
        check_status(status, data)
        return loadjson(data)

    def get_download_url(self, path):
        '''
        Make url for file downloading
        path:  file path.
        
        Return url string
        '''
        base_uri = "http://api-content.dfs.kuaipan.cn/1/fileops/download_file"
        param = self._oauth_param()
        param['root'] = self.root
        param['path'] = path.encode('utf-8')
        url = self._make_url(base_uri, param)
        return url
    
    def fileops_download_file(self, path, start=None, end=None):
        '''
        Download file
        path:  file path.
        start: the start position of downloading, can be None, means download the whole file
        end:   the end position of downloading, can be None, means download to the end of file.
        
        Return a file-like object.
        '''
        url = self.get_download_url(path)
        return openurl(url, timeout=self.timeout, start=start, end=end)
    

    def fileops_thumbnail(self, width, height, path):
        '''
        Download thumbnail of image file.
        
        width:  the width of thumbnail.
        height: the height of thumbnail.
        path: the image file path, file type should be one of [gif, png, jpg, bmp, jpeg, jpe]
        
        Return a file-like object.
        '''
        base_uri = "http://conv.kuaipan.cn/1/fileops/thumbnail"
        param = self._oauth_param()
        param['root'] = self.root
        param['path'] = path.encode('utf-8')
        param['width'] = str(width)
        param['height'] = str(height)
        url = self._make_url(base_uri, param)
        return openurl(url, timeout=self.timeout)
    
    def open_thumbnail(self, pathes=[], thumbnailInfo={'width': '64', 'height': '64'}):
        '''
        Get thumbnails of file list.
        
        pathes:        the file list, like [filepath1, filepath2, ...]
        thumbnailInfo: the thumbnail information, a dict like {'width': _, 'height': _},
      
        Return a dict like
            {
                thumbnails': [
                    {
                        'url': _,
                        'path': _,
                        'file_id': _,
                    },
                ],
            }
        '''
        base_uri = "http://openapi.kuaipan.cn/open/thumbnail"
        param = self._oauth_param()
        param['root'] = self.root
        param['pathes'] = json.dumps([p.encode('utf-8') for p in pathes])
        param['thumbnailInfo'] = json.dumps(thumbnailInfo)
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)
    
    def open_thumbnail302(self, path, width, height):
        '''
        Download the thumbnail of image file by HTTP redirect (code 302).
        
        path: the image file path
        width:  the width of thumbnail.
        height: the height of thumbnail.
        
        
        Return a file-like object.
        '''
        base_uri = "http://openapi.kuaipan.cn/open/thumbnail302"
        param = self._oauth_param()
        param['root'] = self.root
        param['path'] = path.encode('utf-8')
        param['width'] = str(width)
        param['height'] = str(height)
        url = self._make_url(base_uri, param)
        return openurl(url, timeout=self.timeout)
    
    def open_fetch_file_timeline(self, doctype='picture', interval='daily', limit=20, cursor=None):
        '''
        Get the file time line.
        
        doctype: ['picture' | 'document' | 'media' | 'thumbnail'], can be None (means use last value), when <cursor> is not None.
                'picture': ('jpg', 'png', 'gif', 'bmp', 'psd',
                            'ttf', 'jpeg', 'tiff', 'raw', 'nef',
                            'tif', 'emf', 'wmf'),
                'thumbnail': ('jpg', 'png', 'jpeg'),
                'document': ('csv', 'doc', 'docm', 'docx',
                             'dot', 'dotx', 'dps', 'dpt',
                             'et', 'ett', 'pdf', 'pot',
                             'potx', 'pps', 'ppsx', 'ppt',
                             'pptm', 'pptx', 'rtf', 'txt',
                             'wps', 'wpt', 'xls', 'xlsb',
                             'xlsm', 'xlsx', 'xlt', 'xltx'),
                'media': ('mp3', 'wav', 'amr', 'avi', 'mov',
                          'flv', 'ogg', '3gp', 'mpg', 'wma',
                          'wmv', 'rmvb', 'rm', 'mpeg', 'mkv',
                          'mp4', 'ts', 'aac', 'flac',
                          'm4a', 'midi', '3g2', 'asf',
                          'f4v', 'm4v', 'm4r', 'mka', ),
        interval: ['daily'], can be None (means use last value), when <cursor> is not None.
        limit:    how many items each request.
        cursor:   None means get items from beginning, not None means continue last request.
        
        Return a dict like
            {
                'cursor': _, # can be used in next request.
                timeline': [
                    {
                        'date': _,
                        'files': [
                            {
                                'sha1': _,
                                'is_deleted': _,
                                'name': _,
                                'rev': _,
                                'create_time': _,
                                'modify_time': _,
                                'path': _,
                                'size': _,
                                'file_id': _,
                            }
                        ],
                    },
                ],
            }
        
        '''
        base_uri = "http://openapi.kuaipan.cn/open/fetchFileTimeline"
        param = self._oauth_param()
        if doctype is not None:
            param['type'] = doctype
        if interval is not None:
            param['interval'] = interval
        if limit is not None:
            param['limit'] = str(limit)
        if cursor is not None:
            param['cursor'] = cursor
        url = self._make_url(base_uri, param)
        return _readjson(url, timeout=self.timeout)
    

    def fileops_document_view(self, doctype, view, iszip, path):
        '''
        Convert document to html.
        
        doctype: ['pdf' | 'doc' | 'wps' | 'csv' | 'prn' | 'xls' | 'et' | 'ppt' | 'dps' | 'txt' | 'rtf'], document type.
        view:     ['normal' | 'android' | 'iPad' | 'iPhone'], html type.
        iszip:    [True | False], if compress html.
        path:     file path.
        
        Return a file-like object.
        '''
        base_uri = "http://conv.kuaipan.cn/1/fileops/documentView"
        param = self._oauth_param()
        param['root'] = self.root
        param['path'] = path.encode('utf-8')
        param['type'] = doctype
        param['zip'] = '0' if not iszip else '1'
        param['view'] = view
        url = self._make_url(base_uri, param)
        return openurl(url, timeout=self.timeout)


